﻿using System;
using NUnit.Framework;
using Rhino.Mocks;

namespace BSpec.Specs
{
    public abstract class SpecificationBase<T>
    {
        #region Dependency Context

        private readonly DependencyContext _dependencyContext = new DependencyContext(DependencyMode.Real);

        public DependencyContext Dependency
        {
            get { return _dependencyContext; }
        }

        #endregion

        #region Specification Template

        protected T the_subject { get; private set; }

        /// <summary>
        /// Perform required scenario setup
        /// </summary>
        [SetUp]
        public void InitialiseSpecification()
        {
            setup_scenario();
            the_subject = create_subject();
            execute_scenario();
        }

        protected abstract void setup_scenario();

        protected abstract T create_subject();

        /// <summary>
        /// The when under test.
        /// </summary>
        protected abstract void execute_scenario();

        #endregion

        #region Scenario Result

        private bool _scenarioHasResult;
        private object _scenarioResult;

        protected internal void with_scenario_result<R>(Func<R> actionWithResult)
        {
            if (actionWithResult == null)
                throw new ArgumentNullException("actionWithResult", "Can't excecute null with_result action");

            _scenarioResult = actionWithResult();
            _scenarioHasResult = true;
        }

        protected internal R the_scenario_result<R>()
        {
            if (!_scenarioHasResult)
                throw new InvalidOperationException("The scenario was not executed to provide a result");

            return (R)_scenarioResult;
        }

        #endregion

        #region Exception Handling

        private bool _scenarioHasException;
        private Exception _scenarioException;

        protected internal void with_scenario_exception(Action exceptionThrowingAction)
        {
            if (exceptionThrowingAction == null)
                throw new ArgumentNullException("exceptionThrowingAction");

            try
            {
                exceptionThrowingAction();
            }
            catch (Exception ex)
            {
                _scenarioHasException = true;
                _scenarioException = ex;
            }
        }

        protected internal Exception the_scenario_exception()
        {
            if (!_scenarioHasException)
                throw new InvalidOperationException("The scenario execution did not throw an exception");

            return _scenarioException;
        }

        #endregion
    }
}